﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NetfractionHub.Network.Protocol.BASE.Factory
{
	public class FactorySTA : ProtocolMessageFactory
	{
		// Note that this enum is sequential in numbering and entirely unrelated to the error codes.
		public enum ParameterEnum : int
		{
			Severity = 0,
			Code = 1, 
			Description = 2,
			FourCC = 3,
			TimeLeft = 4,
			Token = 5,
			Protocol = 6,
			FieldMissing = 7,
			FieldBad = 8,
			IPAddressV4 = 9,
			IPAddressV6 = 10,
			SessionID = 11,
			InitiatorSID = 12
		}

		// StatusSeverity and StatusErrorCode are identical to the HandlerSTA enums. Probably a bit of a waste?
		public enum StatusSeverity : int
		{
			Success = 0,
			RecoverableError = 1,
			FatalError = 2
		}

		// Errors x0 are generic, with the following xy errors being more specific, relating to x
		// "Most error codes don't have parameters and only make sense in C and I types."
		public enum StatusErrorCode : int
		{
			Generic = 0,
			GenericHubError = 10,
			HubFull = 11,
			HubDisabled = 12,
			GenericLoginError = 20,
			NickInvalid = 21,
			NickTaken = 22,
			InvalidPassword = 23,
			ClientIDTaken = 24,
			CommandDenied = 25,
			RegisteredUsersOnly = 26,
			InvalidPrivateID = 27,
			GenericKickBanDisconnect = 30,
			PermanentlyBanned = 31,
			TemporarilyBanned = 32,
			ProtocolError = 40,
			TransferProtocolUnsupported = 41,
			DirectConnectionFailed = 42,
			RequiredINFFieldMissingOrBad = 43,
			InvalidState = 44,
			RequiredFeatureMissing = 45,
			InvalidIP = 46,
			NoCommonHashFeatureWithHub = 47,
			InvalidFeature = 48,
			C2COrFileTransferError = 50,
			FileNotAvailable = 51,
			FilePartNotAvailable = 52,
			SlotsFull = 53,
			NoCommonHashFeatureWithClient = 54
		}

		public override ProtocolMessage Create(ProtocolMessage.MessageTypeEnum messageType, Dictionary<int, object> parameters)
		{
			ProtocolMessage proto = new ProtocolMessage(messageType, ProtocolFeatureBASE.FeatureActions.ActionSTA);

			if (messageType == ProtocolMessage.MessageTypeEnum.Direct)
			{
				proto.Parameters.Add(new ProtocolParameter(parameters[(int)ParameterEnum.SessionID].ToString()));
				proto.Parameters.Add(new ProtocolParameter(parameters[(int)ParameterEnum.InitiatorSID].ToString()));
			}
			// Combine the severity and code into first parameter.
			int severity = (int)parameters[(int)ParameterEnum.Severity];
			int code = (int)parameters[(int)ParameterEnum.Code];

			// Param is in the form xyy, so the * 100 trick works. Use "d3" format to ensure a 3 digit string
			string codeParam = (severity * 100 + code).ToString("d3");

			proto.Parameters.Add(new ProtocolParameter(codeParam));

			// If the parameters define a custom error message, use that. Otherwise just send a general description for the error code.
			if (!parameters.ContainsKey((int)ParameterEnum.Description))
			{
				proto.Parameters.Add(new ProtocolParameter(GetErrorMessageForCode((StatusErrorCode)code)));
			}
			else
			{
				proto.Parameters.Add(new ProtocolParameter(parameters[(int)ParameterEnum.Description].ToString()));
			}

			foreach (ParameterEnum en in parameters.Keys)
			{
				// This loop is just for flags, these three are already taken care of above.
				if (en != ParameterEnum.Code && en != ParameterEnum.Description && en != ParameterEnum.Severity)
				{
					proto.Parameters.Add(new ProtocolParameter(GetParameterCodeForParameter((ParameterEnum)en), parameters[(int)en].ToString()));
				}
			}

			return proto;
		}

		private string GetParameterCodeForParameter(ParameterEnum param)
		{
			switch (param)
			{
				case ParameterEnum.Severity:  // bit of a problem because Severity and Code are returned as a combined field
					return "Severity";
				case ParameterEnum.Code:
					return "Code";
				case ParameterEnum.Description:
					return "Description";
				case ParameterEnum.FourCC:
					return "FC";
				case ParameterEnum.TimeLeft:
					return "TL";
				case ParameterEnum.Token:
					return "TO";
				case ParameterEnum.Protocol:
					return "PR";
				case ParameterEnum.FieldMissing:
					return "FM";
				case ParameterEnum.FieldBad:
					return "FB";
				case ParameterEnum.IPAddressV4:
					return "I4";
				case ParameterEnum.IPAddressV6:
					return "I6";
				default:
					return "";
			}
		}

		private string GetErrorMessageForCode(StatusErrorCode code)
		{
			switch (code)
			{
				case StatusErrorCode.Generic:
					return "A generic error has occured.";
				case StatusErrorCode.GenericHubError:
					return "A generic hub error has occured.";
				case StatusErrorCode.HubFull:
					return "The hub is currently full.";
				case StatusErrorCode.HubDisabled:
					return "The hub is currently disabled.";
				case StatusErrorCode.GenericLoginError:
					return "An unknown login error has occured.";
				case StatusErrorCode.NickInvalid:
					return "The nickname supplied is invalid.";
				case StatusErrorCode.NickTaken:
					return "The nickname supplied is already in use.";
				case StatusErrorCode.InvalidPassword:
					return "The password supplied is invalid.";
				case StatusErrorCode.ClientIDTaken:
					return "Your client ID is already in use.";
				case StatusErrorCode.CommandDenied:
					return "Access denied.";
				case StatusErrorCode.RegisteredUsersOnly:
					return "You must be a registered user to do that.";
				case StatusErrorCode.InvalidPrivateID:
					return "Your private ID is invalid.";
				case StatusErrorCode.GenericKickBanDisconnect:
					return "You have been kicked, banned or disconnected.";
				case StatusErrorCode.PermanentlyBanned:
					return "You have been permanently banned.";
				case StatusErrorCode.TemporarilyBanned:
					return "You have been temporarily banned.";
				case StatusErrorCode.ProtocolError:
					return "A generic protocol error has occured.";
				case StatusErrorCode.TransferProtocolUnsupported:
					return "The transfer protocol is unsupported.";
				case StatusErrorCode.DirectConnectionFailed:
					return "A direct connection has failed.";
				case StatusErrorCode.RequiredINFFieldMissingOrBad:
					return "A required information field is missing or bad.";
				case StatusErrorCode.InvalidState:
					return "Message cannot be sent in the current state.";
				case StatusErrorCode.RequiredFeatureMissing:
					return "A required feature is missing.";
				case StatusErrorCode.InvalidIP:
					return "The supplied IP address is invalid.";
				case StatusErrorCode.NoCommonHashFeatureWithHub:
					return "The client and hub do not support a common hash algorithm.";
				case StatusErrorCode.InvalidFeature:
					return "The client announced a feature(s) not allowed on this hub.";
				case StatusErrorCode.C2COrFileTransferError:
					return "A client-client or file transfer error has occured.";
				case StatusErrorCode.FileNotAvailable:
					return "The requested file is not available.";
				case StatusErrorCode.FilePartNotAvailable:
					return "The requested file piece is not available.";
				case StatusErrorCode.SlotsFull:
					return "All slots are currently full.";
				case StatusErrorCode.NoCommonHashFeatureWithClient:
					return "The clients do not support a common hash algorithm.";
				default:
					return "";
			}
		}
	}
}
